/*
 * Copyright (C) 2006 Roman Krashanitsa
 *
 * @author Roman Krashanitsa
 *
 * This file is part of paparazzi.
 *
 * paparazzi is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * paparazzi is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with paparazzi; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA. 
 *
 */

package pprz.components;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.text.Position;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import net.jscience.math.MathFP;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import pprz.data_components.Aircraft;
import pprz.data_components.FlightPlan;
import pprz.data_components.GTile;
import pprz.data_components.GeographicConverter;
import pprz.data_components.Message;
import pprz.data_components.FlightPlan.Block;
import pprz.data_components.FlightPlan.Stage;
import pprz.data_components.FlightPlan.Waypoint;
import pprz.data_components.events.AcSelectEvent;
import pprz.data_components.events.AcSelectEventListener;
import pprz.data_components.events.DownlinkMessageEvent;
import pprz.data_components.events.DownlinkMessageEventListener;
import pprz.data_components.events.MapLocationEvent;
import pprz.data_components.events.MapLocationEventListener;
import pprz.data_components.events.MapMarkAddRemoveEvent;
import pprz.data_components.events.MapMarkAddRemoveEventListener;
import pprz.data_components.parsers.FlightPlanParser;

public class FlightPlanContainer extends JScrollPane implements
		DownlinkMessageEventListener, AcSelectEventListener, 
		ActionListener {

	protected static final long serialVersionUID = 1L;

	protected FlightPlanTree current_tree;

	protected HashMap<Integer, FlightPlanTree> acFPTrees;
	protected HashMap<Integer, FlightPlan> acFlightPlans;

	protected HashMap<Integer, Aircraft> acInfo = null; // there is only one //
														// @jve:decl-index=0:
	
	public class FlightPlanTree extends JTree
	implements MouseListener
	{
		
		Rectangle selBlock=null;
		Rectangle selStage=null;
		Rectangle selection=null;
		
		Color blockColor=Color.yellow;
		Color stageColor=Color.green;
		Color selColor=Color.pink;

		FlightPlanTree(TreeModel m)	{super(m);}

		@Override
		protected void paintComponent(Graphics g) {
			g.setColor(blockColor);
			if (selBlock!=null) g.fillRect(0, selBlock.y, this.getWidth(), selBlock.height);
			g.setColor(stageColor);
			if (selStage!=null) g.fillRect(0, selStage.y, this.getWidth(), selStage.height);
			g.setColor(selColor);
			if (selection!=null) g.fillRect(0, selection.y, this.getWidth(), selection.height);
			
			super.paintComponent(g);
		}

		public void setBlockColor(Color c) {blockColor=c;}
		public void setStageColor(Color c) {stageColor=c;}
		public void setSelectionColor(Color c) {selColor=c;}
		
		public void setSelectBlockStage(int i, int j)
		{
			TreePath p=getNextMatch("blocks",0,Position.Bias.Forward);
			TreeNode n=((TreeNode)p.getLastPathComponent()).getChildAt(i);
			
			selBlock=this.getPathBounds(p.pathByAddingChild(n));
			Rectangle r=this.getPathBounds(p.pathByAddingChild(n).pathByAddingChild(n.getChildAt(n.getChildCount()-1)));
			if (r!=null) selBlock.add(r);
			
			
			//skip 'exceptions'
			int k=0; boolean first=false;
			while (k < n.getChildCount() && !first) // find first stage in the block
			{
				if (!n.getChildAt(k).toString().contains("exception"))
					first = true;
				else k++;
			}
			
			if (j + k < n.getChildCount())
			{
				this.expandPath(p.pathByAddingChild(n));
				selStage=this.getPathBounds(p.pathByAddingChild(n).pathByAddingChild(n.getChildAt(j+k)));
			}
			else selStage=null;
			this.repaint(100);
		}
				
		public void mouseClicked(MouseEvent e) {}

		public void mouseEntered(MouseEvent e) {}

		public void mouseExited(MouseEvent e) {}

		public void mousePressed(MouseEvent e) {
			selection=this.getPathBounds(this.getPathForLocation(e.getX(), e.getY()));
			repaint(100);
		}

		public void mouseReleased(MouseEvent e) {
			selection=null;
			repaint(100);
		}
	}
	
	
	class FlightPlanCellRenderer 
	implements TreeCellRenderer {
		
		JLabel text;
		JPanel val;

		DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();

		public FlightPlanCellRenderer() {
			
			text=new JLabel();
			//text.setFont(text.getFont().deriveFont(8));
			val=new JPanel();
			val.setLayout(new BorderLayout());
			val.add(text,BorderLayout.WEST);
			val.setOpaque(false);
		}

		public Component getTreeCellRendererComponent(JTree tree, Object value,
				boolean selected, boolean expanded, boolean leaf, int row,
				boolean hasFocus) {
			
			Component returnValue = null;
			if ((value != null) && (value instanceof DefaultMutableTreeNode)) {
				Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
				if (userObject instanceof String) {
					val.setForeground(Color.black);
					text.setText((String)userObject);
					text.invalidate();
					text.repaint(100);
					val.setEnabled(tree.isEnabled());
					returnValue=val;
				}
			}
			if (returnValue == null) {
				returnValue = defaultRenderer.getTreeCellRendererComponent(
						tree, value, selected, expanded, leaf, row, hasFocus);
			}
			return returnValue;
		}

	}
	
	protected int current_ac; // selected aircraft 

	public FlightPlan parse(String filename) {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		FlightPlanParser handler = new FlightPlanParser();
		try {
			// Parse the input
			SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new File(filename), handler);
		} catch (Exception e) {
			System.err.println("File Read Error: " + filename);
			e.printStackTrace();
			return null;
		}
		
		return handler.getFlightPlan();
	}

	public FlightPlanContainer(HashMap<Integer, Aircraft> acInfo) throws IOException {
		super();
		this.acInfo = acInfo;
		this.getViewport().setOpaque(false);
		acFPTrees = new HashMap<Integer, FlightPlanTree>();
		acFlightPlans = new HashMap<Integer, FlightPlan>();
	}
	
	public FlightPlanTree makeFlightPlanTree(FlightPlan fp)
	{
		DefaultMutableTreeNode root=new DefaultMutableTreeNode(new String("/"));
		DefaultMutableTreeNode wp=new DefaultMutableTreeNode(new String("waypoints"));
		root.add(wp);
		for (Waypoint w : fp.getWaypoints())
		{
			DefaultMutableTreeNode node = new DefaultMutableTreeNode();
			node.setUserObject(w.toString());
			wp.add(node);
		}
		DefaultMutableTreeNode bl=new DefaultMutableTreeNode(new String("blocks"));
		root.add(bl);
		for (Block b : fp.getBlocks())
		{
			DefaultMutableTreeNode node = new DefaultMutableTreeNode();
			node.setUserObject(b.toString());
			for (int i=0; i<b.stages.size(); i++) {
				DefaultMutableTreeNode child=new DefaultMutableTreeNode();
				child.setUserObject(b.stages.get(i).toString());
				node.add(child);
			}
			bl.add(node);
		}
		
		return new FlightPlanTree(new DefaultTreeModel(root));
		
	}

/*	public void parseXML(String fileName) {
		current_tree = makeFlightPlanTree(parse(fileName));
		current_tree.setLargeModel(true);
		setViewportView(current_tree);
	}
*/
	protected void check_add(int ac) {
		if (!(acFlightPlans.containsKey(ac))) {
			Aircraft a = acInfo.get(ac);
			String s = a.get_flight_plan();
			if (s == null)
				return;
			if (new File(s).exists()) {
				FlightPlan fp=parse(s);
				FlightPlanTree t = makeFlightPlanTree(fp);
				t.addMouseListener(t);
				t.setOpaque(false);
				TreeCellRenderer renderer = new FlightPlanCellRenderer();
				t.setCellRenderer(renderer);
				acFPTrees.put(ac, t);
				acFlightPlans.put(ac, fp);
			}
		}
	}

	public void selectAC(int ac) {
		this.current_ac = ac;
		check_add(ac);
		if (current_tree!=null) current_tree.setVisible(false);
		current_tree = acFPTrees.get(ac);
		current_tree.setVisible(true);
		FlightPlan fp=acFlightPlans.get(ac);

		setViewportView(current_tree);

		fireMapLocationEvent(new MapLocationEvent(this, fp.getLocation()));
		fireMapMarkAddRemoveEvent(new MapMarkAddRemoveEvent(this, 0));
		fireMapMarkAddRemoveEvent(new MapMarkAddRemoveEvent(this, 0, new Area(
				new Ellipse2D.Double(-0.5, -0.5, 1, 1)), fp.getLocation(), new Point2D.Double(10, 10)));
		int i = 1000;
		for (int index=0; index<fp.getWaypoints().size(); index++ ) // add waypoint symbols on the map
		{
			fireMapMarkAddRemoveEvent(new MapMarkAddRemoveEvent(this, i++,
					new Area(new Ellipse2D.Double(-0.5, -0.5, 1, 1)),
					fp.getWaypointLoc(index), new Point2D.Double(10, 10)));
		}
	}

	// set current block and stage in the flight plan
	public void set(int ac_id, int block, int stage) {
		FlightPlanTree set_tree = (ac_id == this.current_ac) ? current_tree : acFPTrees.get(ac_id);
		if (set_tree == null) return;
		set_tree.setSelectBlockStage(block, stage);
		Stage s=acFlightPlans.get(ac_id).getBlocks().get(block).stages.get(stage);
		if (s.op.compareToIgnoreCase("circle")==0)
		{
			int i=s.att.names.indexOf("wp");
		}
		// ask map to display info from the flight plan
/*		TreePath t = set_tree.getNextMatch("blocks", 0, Position.Bias.Forward);
		DefaultMutableTreeNode node = (DefaultMutableTreeNode) t
				.getLastPathComponent();
		ITag tag = ((ITag) node.getUserObject());
		Attributes a = tag.getAttributes();
		if (tag.name.compareToIgnoreCase("circle") == 0) {
			String wpName = a.getValue("wp");
			Double radius = Double.parseDouble(a.getValue("radius"));
			Point2D.Double wp = waypoints.get(wpName);
			if (wp != null) {
				Point p1 = GTile.UTM2Pix(wp.y - radius, wp.x - radius, 0);
				Point p2 = GTile.UTM2Pix(wp.y + radius, wp.x + radius, 0);
				Point pc = GTile.UTM2Pix(wp.y, wp.x, 0);
				fireMapMarkAddRemoveEvent(new MapMarkAddRemoveEvent(this, 2000));
				fireMapMarkAddRemoveEvent(new MapMarkAddRemoveEvent(this, 2000,
						new Ellipse2D.Double(-0.5, -0.5, 1, 1), pc, 
						new Point2D.Double(p2.x - p1.x, p1.y - p2.y)));
			}
		}
*/
	}

	public void messageEvent(DownlinkMessageEvent ev) {
		Message m = ev.getMessage();
		int ac = m.acRec().get_id();
		// process only downlink messages id=10 from currect AC
		if (m.msgClass() != 0 || m.id() != 10)
			return;
		assert (ac != 0);
		check_add(ac);
		set( m.acRec().get_id(), //aircraft 
			(int) MathFP.toLong(m.getFields().get(0)[0]), // block
			(int) MathFP.toLong(m.getFields().get(1)[0])); // stage
	}

	public void actionPerformed(ActionEvent arg0) {
		if (arg0.getSource().getClass() == AcInfoContainer.class)
			selectAC(((AcInfoContainer) arg0.getSource()).getSelected());
	}

	public void acSelectEvent(AcSelectEvent ev) {
		// TODO Auto-generated method stub
		selectAC(ev.getAc());
	}

	// These methods allows classes to register for MyEvents
	public void addMapMarkAddRemoveEventListener(
			MapMarkAddRemoveEventListener listener) {
		listenerList.add(MapMarkAddRemoveEventListener.class, listener);
	}

	// This methods allows classes to unregister for MyEvents
	public void removeMapMarkAddRemoveEventListener(
			MapMarkAddRemoveEventListener listener) {
		listenerList.remove(MapMarkAddRemoveEventListener.class, listener);
	}

	protected void fireMapMarkAddRemoveEvent(MapMarkAddRemoveEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == MapMarkAddRemoveEventListener.class) {
				((MapMarkAddRemoveEventListener) listeners[i + 1])
						.mapMarkAddRemoveEvent(evt);
			}
		}
	}

	// These methods allows classes to register for MyEvents
	public void addMapLocationEventListener(MapLocationEventListener listener) {
		listenerList.add(MapLocationEventListener.class, listener);
	}

	// This methods allows classes to unregister for MyEvents
	public void removeMapLocationEventListener(MapLocationEventListener listener) {
		listenerList.remove(MapLocationEventListener.class, listener);
	}

	protected void fireMapLocationEvent(MapLocationEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == MapLocationEventListener.class) {
				((MapLocationEventListener) listeners[i + 1])
						.mapLocationEvent(evt);
			}
		}
	}

}
